#pragma once

#include "pair.hpp"
#include "construct.hpp"

namespace zuck
{
	template<typename T>
	[[nodiscard]] constexpr T(max) (const T & left, const T & right)
	{
		return left < right ? right : left;
	}

	template<typename T>
	[[nodiscard]] constexpr T(min) (const T & left, const T & right)
	{
		return left < right ? left : right;
	}

	template<typename Size_>
	[[nodiscard]] constexpr bool out_of_range(const Size_ ind, const Size_ size_)
	{
		return static_cast<bool>(ind < 0 || ind >= size_);
	}

	template<typename T>
	[[nodiscard]] constexpr zuck::enable_if_t<is_object_v<T>, T*> address_of(T & _Obj)noexcept
	{
		return reinterpret_cast<T*>(&const_cast<char&>(reinterpret_cast<const volatile char&>(_Obj)));
	}

	template<typename iter>
	inline void iter_swap(iter left, iter right)
	{
		using Valty = zuck::iter_traits_t<iter>;

		Valty tmp = *left;
		*left = *right;
		*right = tmp;
	}

	template<typename T, ::zuck::enable_if_t<::zuck::is_integral_v<T>, T> = 0>
	void (swap)(T& left, T& right)
	{
		left ^= right ^= left ^= right;
	}

	template<typename T>
	inline void (swap)(T& left, T& right)noexcept
	{
		if (left != right) {
			auto _Tmp = static_cast<T&&>(right);
			right = static_cast<T&&>(left);
			left = static_cast<T&&>(_Tmp);
		}
	}

	template<typename T>
	inline void (swap)(T& left, T&& right)noexcept
	{
		if (left != right) {
			auto _Tmp = zuck::forward<T>(right);
			right = static_cast<T&&>(left);
			left = zuck::forward<T>(_Tmp);
		}
	}

	template<typename T>
	inline void (swap)(T&&, T&) = delete;

	template<typename T>
	void (swap)(T&&, T&&) = delete;

	template<typename Iterator, typename _Diff_t = iter_diff_t<Iterator>>
	[[nodiscard]] inline _Diff_t distance(Iterator _Begin, Iterator _End)
	{
		_Diff_t _Cnt = 0;
		constexpr bool random_iter_flag = ::zuck::is_random_access_iterator_v<Iterator>;

		if constexpr (random_iter_flag) {
			return _End - _Begin;
		}
		for (; _Begin != _End; ++_Begin) {
			++_Cnt;
		}
		return _Cnt;
	}

	template<typename Iterator, typename _Diff_t = iter_diff_t<Iterator>>
	[[nodiscard]] Iterator advance(Iterator _Iter, _Diff_t _Offset)
	{
		constexpr bool random_iter_flag =
			zuck::is_random_access_iterator_v<
			typename Iterator::iterator_category>;

		if constexpr (random_iter_flag) {
			return _Iter + _Offset;
		}
		for (; _Offset; --_Offset) {
			++_Iter;
		}
		return _Iter;
	}

	template<typename T>
	inline T exchange(T& obj, T&& _new_val)
	{
		static_assert(!is_void_v<T>,
			"Invalid type name \"void\"");

		auto _old = zuck::move(obj);
		obj = zuck::move(_new_val);
		return _old;
	}

	template<typename Iter, typename Valty = iter_traits_t<Iter>>
	void fill(Iter first, Iter last, Valty val)
	{
		auto _First = zuck::move(first);
		auto const _Last = zuck::move(last);

		for (; _First != _Last; ++_First) {
			*_First = val;
		}
	}

	template<typename Iter1, typename Iter2>
	void fill(Iter1 _Srcfirst, Iter1 _Srclast, Iter2 _DesBegin, Iter2 _DesEnd)
	{
		using Valty = zuck::iter_traits_t<Iter1>;

		auto _First1 = zuck::move(_Srcfirst);
		auto const _Last1 = zuck::move(_Srclast);
		auto _First2 = zuck::move(_DesBegin);
		auto const _Last2 = zuck::move(_DesEnd);

		for (; _First2 != _Last2 && _First1 != _Last1;
			++_First1, ++_First2)
		{
			*_First2 = *_First1;
		}
	}


	template<typename T>
	class reference_wrapper {
	public:
		using type = T;
	public:
		template<typename Type>
		reference_wrapper(remove_reference<T>&& __ref)
		{
			T& _ref = static_cast<Type&&>(__ref);
			_m_var = address_of(_ref);
		}

		T& get() { return *_m_var; }

		inline operator T& ()const noexcept { return *_m_var; }

	private:
		T* _m_var = null;
	};

	template<typename T>
	[[nodiscard]] inline reference_wrapper<T> ref(T & _m_var)
	{
		return reference_wrapper<T>(_m_var);
	}

	template<typename T, T...vals>
	class integer_sequence {
	public:
		static_assert(is_integral_v<T>,
			"template parameter \"T\" must be integer type");

		using size_type = zuck::size_t;

		[[nodiscard]] static constexpr size_type size()
		{
			return sizeof ...(vals);
		}
	};
}

